home *** CD-ROM | disk | FTP | other *** search
/ Palm Pilot Collection / Palm Pilot Collection (Explore the World of Software) (1998).iso / apps / mathpad.exe / MPIMPORT.C < prev    next >
C/C++ Source or Header  |  1997-09-28  |  20KB  |  676 lines

  1. /* MpImport: Imports a set of MathPad database records from a text file
  2.  * created by MpExport back into a MathPad database file.
  3.  *
  4.  * Written in plain vanilla ANSI standard C; should compile pretty easily
  5.  * with any regular C compiler.  Released to the public domain.
  6.  *
  7.  * Version 1.0, 28 Sep 1997, Rick Huebner
  8.  */
  9. #include <stddef.h>
  10. #include <stdlib.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <time.h>
  14. #include <ctype.h>
  15.  
  16. #include "mpdb.h"
  17.  
  18. /* MathPad records are held in memory as a linked list of these structs */
  19. typedef struct Record {
  20.    struct Record *next;
  21.    Byte catnum, places;
  22.    Boolean secret, stripzeros;
  23.    char *text;
  24. } Record, *RecordPtr;
  25.  
  26. typedef enum { YES, NO, ALL } ReplyType;
  27.  
  28. /* Global variables */
  29. DatabaseHdrType Hdr;
  30. MathPadAppInfoType MpInfo;
  31. RecordPtr RecordList = NULL;
  32. Word NumRecords = 0;
  33.  
  34. /* Function prototypes */
  35. void LoadDB(FILE *db);
  36. void ProcessImports(FILE *fp);
  37. void SaveDB(FILE *db);
  38. RecordPtr LoadImport(FILE *fp);
  39. int  ReadLine(char *buff, int size, FILE *fp);
  40. ReplyType Confirm(char *prefix, char *title);
  41. void AddRecord(RecordPtr recptr);
  42. RecordPtr FindRecord(char *title);
  43. void ReplaceRecord(RecordPtr oldptr, RecordPtr newptr);
  44. void FreeRecords(void);
  45.  
  46.  
  47.  
  48. void main(int argc, char *argv[]) {
  49.    FILE *db, *text;
  50.    char *outname;
  51.  
  52.    /* Check for the proper command line format */
  53.    if (argc < 3 || argc > 4) {
  54.       puts("Format: MPIMPORT OldDbFileName TextFileName [NewDbFileName]\n");
  55.       puts("Specify NewDbFileName to create a new database file and leave");
  56.       puts("OldDbFileName untouched as a backup, or omit NewDbFileName");
  57.       puts("to update OldDbFileName in place.");
  58.       exit(1);
  59.    }
  60.  
  61.    /* Load the old database into memory.  NOTE: "rb"
  62.       indicates "binary" file type with no CR/LF text translation.
  63.       You may need to change this to "r" if "rb" isn't supported
  64.       by your compiler, but call setmode() or whatever as required
  65.       to make sure no translations are done for this file. */
  66.    db = fopen(argv[1], "rb");
  67.    if (!db) {
  68.       printf("Can't open \"%s\": %s", argv[1], strerror(errno));
  69.       exit(1);
  70.    }
  71.    LoadDB(db);
  72.    fclose(db);
  73.  
  74.    /* Process the imports from the text file */
  75.    text = fopen(argv[2], "r");
  76.    if (!text) {
  77.       printf("Can't open \"%s\": %s", argv[2], strerror(errno));
  78.       exit(1);
  79.    }
  80.    ProcessImports(text);
  81.    fclose(text);
  82.  
  83.    /* Write the updated database to disk */
  84.    outname = (argc > 3) ? argv[3] : argv[1];
  85.    db = fopen(outname, "wb");
  86.    if (!db) {
  87.       printf("Can't open \"%s\": %s", outname, strerror(errno));
  88.       exit(1);
  89.    }
  90.    SaveDB(db);
  91.    fclose(db);
  92.  
  93.    /* Done; clean up and exit */
  94.    FreeRecords();
  95. }
  96.  
  97.  
  98.  
  99. /* Load the source MathPad database file into memory */
  100. void LoadDB(FILE *db) {
  101.    size_t length;
  102.    long listpos, textpos;
  103.    Word recnum;
  104.    RecordListType reclist;
  105.    RecordEntryPtr entries, entryptr;
  106.    MathPadItemType item;
  107.    RecordPtr recptr;
  108.  
  109.    /* Read the database header */
  110.    length = offsetof(DatabaseHdrType, recordList);
  111.    if (fread(&Hdr, 1, length, db) != length) {
  112.       perror("Error reading database header");
  113.       exit(1);
  114.    }
  115.  
  116.    /* Make sure we know how to handle this database file */
  117.    if (strncmp((char *)&Hdr.type, MathPadType, 4) || strncmp((char *)&Hdr.creator, MathPadCreator, 4)) {
  118.       puts("Not a MathPad database file");
  119.       exit(1);
  120.    }
  121.  
  122.    SwapWord(&Hdr.version);
  123.    if (Hdr.version != MathPadVersion) {
  124.       puts("Don't know how to read this version of MathPad database.");
  125.       puts("Please get the latest version of MpImport and try again.");
  126.       exit(1);
  127.    }
  128.    SwapWord(&Hdr.version);
  129.  
  130.    /* Remember our current file position so we can come back here
  131.       to read the (first?) record list header. */
  132.    listpos = ftell(db);
  133.  
  134.    /* Go to and read the app info block (category data). */
  135.    SwapDWord(&Hdr.appInfoID);
  136.    fseek(db, Hdr.appInfoID, SEEK_SET);
  137.    if (fread(&MpInfo, 1, sizeof(MpInfo), db) != sizeof(MpInfo)) {
  138.       perror("Error reading database app info block");
  139.       exit(1);
  140.    }
  141.  
  142.    /* Process the linked list of record lists */
  143.    do {
  144.       /* Go to and read this record list header */
  145.       fseek(db, listpos, SEEK_SET);
  146.       length = offsetof(RecordListType, firstEntry);
  147.       if (fread(&reclist, 1, length, db) != length) {
  148.          perror("Error reading database record list");
  149.          exit(1);
  150.       }
  151.  
  152.       SwapWord(&reclist.numRecords);
  153.       if (reclist.numRecords > 0) {
  154.          /* Allocate memory for the correct size array of record entries */
  155.          length = reclist.numRecords * sizeof(RecordEntryType);
  156.          entries = malloc(length);
  157.          if (!entries) {
  158.             perror("malloc() failure");
  159.             exit(1);
  160.          }
  161.  
  162.          /* Read the record list entries into our array */
  163.          if (fread(entries, 1, length, db) != length) {
  164.             perror("Error reading database record entries");
  165.             exit(1);
  166.          }
  167.  
  168.          /* Load each record in the record entry array */
  169.          for (entryptr = entries, recnum = 0; recnum < reclist.numRecords; ++recnum, ++entryptr) {
  170.             /* Allocate a new node for our linked list of MathPad records */
  171.             recptr = malloc(sizeof(Record));
  172.             if (!recptr) {
  173.                perror("malloc() failure");
  174.                exit(1);
  175.             }
  176.  
  177.             /* Extract the category number and Secret flag from this entry */
  178.             recptr->catnum = entryptr->attributes & dmRecAttrCategoryMask;
  179.             recptr->secret = (entryptr->attributes & dmRecAttrSecret) != 0;
  180.  
  181.             /* Go to this record in the file */
  182.             SwapDWord(&entryptr->localChunkID);
  183.             fseek(db, entryptr->localChunkID, SEEK_SET);
  184.  
  185.             /* Read the MathPad record header and extract the settings */
  186.             length = offsetof(MathPadItemType, text);
  187.             if (fread(&item, 1, length, db) != length) {
  188.                perror("Error reading database record");
  189.                exit(1);
  190.             }
  191.             recptr->places = item.places;
  192.             recptr->stripzeros = item.stripzeros;
  193.  
  194.             /* Remember where the record text starts */
  195.             textpos = ftell(db);
  196.  
  197.             /* Count the number of bytes of text in this record */
  198.             length = 1;
  199.             while (fgetc(db) > 0)
  200.                ++length;
  201.  
  202.             /* Allocate a block of memory to hold the record text */
  203.             recptr->text = malloc(length);
  204.             if (!recptr->text) {
  205.                perror("malloc() failure");
  206.                exit(1);
  207.             }
  208.  
  209.             /* Go back to the start of the record text and read it
  210.                into the allocated block */
  211.             fseek(db, textpos, SEEK_SET);
  212.             fread(recptr->text, 1, length, db);
  213.  
  214.             /* Add this loaded MathPad record to our linked list */
  215.             AddRecord(recptr);
  216.          }
  217.  
  218.          /* Free this record entry array */
  219.          free(entries);
  220.       }
  221.  
  222.       /* Get the position of the next record list in the chain, if any */
  223.       SwapDWord(&reclist.nextRecordListID);
  224.       listpos = reclist.nextRecordListID;
  225.    } while (listpos);
  226. }
  227.  
  228.  
  229.  
  230. /* Import each record from the text file into the set of records we
  231.  * loaded into memory
  232.  */
  233. void ProcessImports(FILE *fp) {
  234.    RecordPtr newrec, recptr;
  235.    ReplyType reply;
  236.    Boolean allok = FALSE;
  237.  
  238.    /* Import records until EOF */
  239.    while ((newrec = LoadImport(fp)) != NULL) {
  240.       /* Look for an existing record with the same title */
  241.       recptr = FindRecord(newrec->text);
  242.  
  243.       /* If no such record already exists, go ahead an add it */
  244.       if (!recptr)
  245.          AddRecord(newrec);
  246.       /* If this import is different than the existing record, ask
  247.          user whether to overwrite the existing one or add this
  248.          import as a separate record */
  249.       else if (strcmp(newrec->text, recptr->text) ||
  250.                newrec->catnum != recptr->catnum ||
  251.                newrec->secret != recptr->secret ||
  252.                newrec->places != recptr->places ||
  253.                newrec->stripzeros != recptr->stripzeros) {
  254.          reply = allok ? YES : Confirm("Overwrite", recptr->text);
  255.          switch (reply) {
  256.             case ALL:
  257.                allok = TRUE;
  258.                /* fallthrough */
  259.             case YES:
  260.                ReplaceRecord(recptr, newrec);
  261.                break;
  262.             case NO:
  263.                AddRecord(newrec);
  264.                break;
  265.          }
  266.       /* If this import exactly matches the existing record, skip it */
  267.       } else {
  268.          free(newrec->text);
  269.          free(newrec);
  270.       }
  271.    }
  272. }
  273.  
  274.  
  275.  
  276. /* Save the updated MathPad records to disk */
  277. void SaveDB(FILE *db) {
  278.    size_t length;
  279.    long listpos;
  280.    char *textptr;
  281.    RecordListType reclist;
  282.    RecordEntryPtr entries, entryptr;
  283.    MathPadItemType item;
  284.    RecordPtr recptr;
  285.  
  286.    /* Save space in the file for the database header.  We'll come
  287.       back and rewrite this on the second pass, after we know the
  288.       file offset for the app info block. */
  289.    length = offsetof(DatabaseHdrType, recordList);
  290.    if (fwrite(&Hdr, 1, length, db) != length) {
  291.       perror("Error writing database header");
  292.       exit(1);
  293.    }
  294.  
  295.    /* Write the record list header */
  296.    reclist.nextRecordListID = 0;
  297.    reclist.numRecords = NumRecords;
  298.    SwapWord(&reclist.numRecords);
  299.  
  300.    length = offsetof(RecordListType, firstEntry);
  301.    if (fwrite(&reclist, 1, length, db) != length) {
  302.       perror("Error writing database record list");
  303.       exit(1);
  304.    }
  305.  
  306.    /* Allocate memory for the correct size array of record entries */
  307.    length = NumRecords * sizeof(RecordEntryType);
  308.    entries = (RecordEntryPtr)malloc(length);
  309.    if (!entries) {
  310.       perror("malloc() failure");
  311.       exit(1);
  312.    }
  313.  
  314.    /* Save space in the file for the record list entries.  We'll come back
  315.       and rewrite these with real values after we've determined the file
  316.       offset and attributes for each. */
  317.    listpos = ftell(db);
  318.    if (fwrite(entries, 1, length, db) != length) {
  319.       perror("Error writing database record entries");
  320.       exit(1);
  321.    }
  322.  
  323.    /* Write the app info block and save its location in the header */
  324.    Hdr.appInfoID = ftell(db);
  325.    SwapDWord(&Hdr.appInfoID);
  326.    if (fwrite(&MpInfo, 1, sizeof(MpInfo), db) != sizeof(MpInfo)) {
  327.       perror("Error writing database app info block");
  328.       exit(1);
  329.    }
  330.  
  331.    /* Write each MathPad record in our linked list */
  332.    entryptr = entries;
  333.    recptr = RecordList;
  334.    while (recptr) {
  335.       /* Fill in the record entry values for each record as it's written */
  336.       entryptr->localChunkID = ftell(db);
  337.       SwapDWord(&entryptr->localChunkID);
  338.       entryptr->attributes = recptr->catnum;
  339.       if (recptr->secret)
  340.          entryptr->attributes |= dmRecAttrSecret;
  341.       memset(entryptr->uniqueID, 0, sizeof(entryptr->uniqueID));
  342.       ++entryptr;
  343.  
  344.       /* Write the MathPad record header */
  345.       item.places = recptr->places;
  346.       item.stripzeros = recptr->stripzeros;
  347.       length = offsetof(MathPadItemType, text);
  348.       if (fwrite(&item, 1, length, db) != length) {
  349.          perror("Error writing database record");
  350.          exit(1);
  351.       }
  352.  
  353.       /* Write the MathPad record text */
  354.       textptr = recptr->text;
  355.       do {
  356.          fputc(*textptr, db);
  357.       } while (*textptr++);
  358.  
  359.       recptr = recptr->next;
  360.    }
  361.  
  362.    /* Set the times in the header to the current time
  363.       to prevent "invalid file deleted" problems on Macs */
  364.    Hdr.creationDate = Hdr.modificationDate = Hdr.lastBackupDate = time(NULL);
  365.    SwapDWord(&Hdr.creationDate);
  366.    SwapDWord(&Hdr.modificationDate);
  367.    SwapDWord(&Hdr.lastBackupDate);
  368.  
  369.    /* Rewrite the database header with the final values */
  370.    rewind(db);
  371.    length = offsetof(DatabaseHdrType, recordList);
  372.    if (fwrite(&Hdr, 1, length, db) != length) {
  373.       perror("Error rewriting database header");
  374.       exit(1);
  375.    }
  376.  
  377.    /* Rewrite the record entries with the final values */
  378.    fseek(db, listpos, SEEK_SET);
  379.    length = NumRecords * sizeof(RecordEntryType);
  380.    if (fwrite(entries, 1, length, db) != length) {
  381.       perror("Error rewriting database record entries");
  382.       exit(1);
  383.    }
  384. }
  385.  
  386.  
  387.  
  388. /* Load one import record from the text file into memory */
  389. RecordPtr LoadImport(FILE *fp) {
  390.    int linelength, catlength, textlength, i;
  391.    char buff[256], catname[dmCategoryLength], *start, *end;
  392.    RecordPtr newrec;
  393.  
  394.    /* Read the first line of import text.  Ignore any blank lines
  395.       so that excess trailing blank lines after the last record
  396.       don't get loaded as a blank import record. */
  397.    do {
  398.       linelength = ReadLine(buff, sizeof(buff), fp);
  399.    } while (linelength == 1);
  400.  
  401.    /* If we couldn't read anything, we're at EOF */
  402.    if (linelength < 1)
  403.       return NULL;
  404.  
  405.    /* Allocate a new MathPad record struct to hold the imported record */
  406.    newrec = malloc(sizeof(Record));
  407.    if (!newrec) {
  408.       perror("malloc() failure");
  409.       exit(1);
  410.    }
  411.  
  412.    /* If the category/secret line is missing, use defaults */
  413.    if (strncmp(buff, CategoryLine, CatTestLength)) {
  414.       newrec->catnum = dmUnfiledCategory;
  415.       newrec->secret = FALSE;
  416.    } else {
  417.       /* Extract the category name from between the quotes */
  418.       start = strchr(buff, '\"') + 1;
  419.       end = strchr(start, '\"');
  420.       catlength = end - start;
  421.       if (catlength >= dmCategoryLength)
  422.          catlength = dmCategoryLength - 1;
  423.       memcpy(catname, start, catlength);
  424.       catname[catlength] = '\0';
  425.  
  426.       /* Look up the category number if it's already in the database */
  427.       for (newrec->catnum = 0; newrec->catnum < dmRecNumCategories; ++newrec->catnum) {
  428.          if (!strcmp(MpInfo.appinfo.categoryLabels[newrec->catnum], catname))
  429.             break;
  430.       }
  431.  
  432.       /* If the category isn't already in the database, add it if possible */
  433.       if (newrec->catnum >= dmRecNumCategories) {
  434.          /* Find the first unused category name slot */
  435.          for (newrec->catnum = 0; newrec->catnum < dmRecNumCategories; ++newrec->catnum) {
  436.             if (!MpInfo.appinfo.categoryLabels[newrec->catnum][0])
  437.                break;
  438.          }
  439.  
  440.          /* If all category names are in use, revert import to Unfiled */
  441.          if (newrec->catnum >= dmRecNumCategories)
  442.             newrec->catnum = dmUnfiledCategory;
  443.          else {
  444.             /* Copy the new category name into the unused slot */
  445.             strcpy(MpInfo.appinfo.categoryLabels[newrec->catnum], catname);
  446.             /* Set the new category's unique ID to the next unused value */
  447.             do {
  448.                ++MpInfo.appinfo.lastUniqID;
  449.                for (i = 0; i < dmRecNumCategories; ++i)
  450.                   if (MpInfo.appinfo.categoryUniqIDs[i] == MpInfo.appinfo.lastUniqID)
  451.                      break;
  452.             } while (i < dmRecNumCategories);
  453.             MpInfo.appinfo.categoryUniqIDs[newrec->catnum] = MpInfo.appinfo.lastUniqID;
  454.          }
  455.       }
  456.  
  457.       /* Extract the secret flag value from after the = sign */
  458.       start = strchr(end+1, '=') + 1;
  459.       newrec->secret = atoi(start) != 0;
  460.  
  461.       /* Replace this line of import text with the next one */
  462.       linelength = ReadLine(buff, sizeof(buff), fp);
  463.       if (linelength < 1) {
  464.          free(newrec);
  465.          return NULL;
  466.       }
  467.    }
  468.  
  469.    /* If the places/stripzeros line is missing, use defaults */
  470.    if (strncmp(buff, PlacesLine, PlacesTestLength)) {
  471.       newrec->places = 14;
  472.       newrec->stripzeros = TRUE;
  473.    } else {
  474.       /* Extract the decimal places setting from after the first = */
  475.       start = strchr(buff, '=') + 1;
  476.       newrec->places = (Byte)atoi(start);
  477.  
  478.       /* Extract the stripzeros flag value from after the second = */
  479.       start = strchr(start, '=') + 1;
  480.       newrec->stripzeros = atoi(start) != 0;
  481.  
  482.       /* Replace this line of import text with the next one */
  483.       linelength = ReadLine(buff, sizeof(buff), fp);
  484.       if (linelength < 1) {
  485.          free(newrec);
  486.          return NULL;
  487.       }
  488.    }
  489.  
  490.    /* Allocate a block of memory to hold the first line of record text */
  491.    textlength = linelength;
  492.    newrec->text = malloc(textlength);
  493.    if (!newrec->text) {
  494.       perror("malloc() failure");
  495.       exit(1);
  496.    }
  497.    memcpy(newrec->text, buff, textlength);
  498.  
  499.    /* Since ftell/fseek are unreliable when using text translation mode,
  500.       we can't just read the text to see how many bytes we need and then
  501.       back up and re-read it into the memory block like we normally would.
  502.       Instead, we'll read it in one pass, extending the memory block as
  503.       required to append each line as we go. */
  504.    while ((linelength = ReadLine(buff, sizeof(buff), fp)) > 0) {
  505.       newrec->text = realloc(newrec->text, textlength+linelength);
  506.       if (!newrec->text) {
  507.          perror("realloc() failure");
  508.          exit(1);
  509.       }
  510.       memcpy(newrec->text+textlength, buff, linelength);
  511.       textlength += linelength;
  512.    }
  513.    newrec->text[textlength-1] = '\0';
  514.  
  515.    return newrec;
  516. }
  517.  
  518.  
  519.  
  520. /* Read one line of import text, translating the end of line character
  521.  * to the one used within the PalmPilot.  Returns -1 for EOF, 0
  522.  * for end of record, or > 0 for bytes in line.
  523.  */
  524. int ReadLine(char *buff, int size, FILE *fp) {
  525.    char *p;
  526.  
  527.    if (!fgets(buff, size, fp))
  528.       return -1;
  529.  
  530.    if (!strncmp(buff, SeparatorLine, SepTestLength))
  531.       return 0;
  532.  
  533.    p = buff;
  534.    while (*p && *p != '\n' && *p != '\r')
  535.       ++p;
  536.    *p++ = PilotEOL;
  537.    *p = '\0';
  538.  
  539.    return p - buff;
  540. }
  541.  
  542.  
  543.  
  544. /* Ask the user for confirmation and return YES, NO, or ALL */
  545. ReplyType Confirm(char *prefix, char *title) {
  546.    char *p, buff[128];
  547.  
  548.    while (TRUE) {
  549.       printf(prefix);
  550.       if (title) {
  551.          printf(" \"");
  552.          p = title;
  553.          while (*p && *p != PilotEOL) {
  554.             putchar(*p);
  555.             ++p;
  556.          }
  557.          putchar('\"');
  558.       }
  559.       printf(" (Yes/No/All)? ");
  560.       fflush(stdout);
  561.  
  562.       fflush(stdin);
  563.       gets(buff);
  564.  
  565.       switch (toupper(buff[0])) {
  566.          case 'Y':
  567.             return YES;
  568.          case 'N':
  569.             return NO;
  570.          case 'A':
  571.             return ALL;
  572.       }
  573.    }
  574. }
  575.  
  576.  
  577.  
  578. /* Append a new MathPad record to the end of the linked list */
  579. void AddRecord(RecordPtr recptr) {
  580.    RecordPtr lastnode = (RecordPtr)&RecordList;
  581.  
  582.    while (lastnode->next)
  583.       lastnode = lastnode->next;
  584.  
  585.    lastnode->next = recptr;
  586.    recptr->next = NULL;
  587.  
  588.    ++NumRecords;
  589. }
  590.  
  591.  
  592.  
  593. /* Search the linked list for a record with the specified tile */
  594. RecordPtr FindRecord(char *title) {
  595.    RecordPtr currnode = RecordList;
  596.    char *p1, *p2;
  597.  
  598.    while (currnode) {
  599.       p1 = title;
  600.       p2 = currnode->text;
  601.       while (*p1 && *p1 != PilotEOL && *p1 == *p2) {
  602.          ++p1;
  603.          ++p2;
  604.       }
  605.       if (*p1 == *p2)
  606.          return currnode;
  607.  
  608.       currnode = currnode->next;
  609.    }
  610.  
  611.    return NULL;
  612. }
  613.  
  614.  
  615.  
  616. /* Replace the specified record in the linked list */
  617. void ReplaceRecord(RecordPtr oldptr, RecordPtr newptr) {
  618.    RecordPtr currnode, prevnode = (RecordPtr)&RecordList;
  619.  
  620.    while (prevnode->next) {
  621.       currnode = prevnode->next;
  622.       if (currnode != oldptr)
  623.          prevnode = currnode;
  624.       else {
  625.          prevnode->next = newptr;
  626.          newptr->next = oldptr->next;
  627.          free(oldptr->text);
  628.          free(oldptr);
  629.          break;
  630.       }
  631.    }
  632. }
  633.  
  634.  
  635.  
  636. /* Free all memory used by the linked list */
  637. void FreeRecords(void) {
  638.    RecordPtr currnode = RecordList, next;
  639.  
  640.    while (currnode) {
  641.       free(currnode->text);
  642.       next = currnode->next;
  643.       free(currnode);
  644.       currnode = next;
  645.    }
  646.  
  647.    RecordList = NULL;
  648. }
  649.  
  650.  
  651.  
  652. /* Routines to translate a value between Motorola-style storage format 
  653.  * (MSB first) and Intel-style storage format (LSB first).
  654.  */
  655. #ifdef LITTLE_ENDIAN
  656. void SwapWord(void *p) {
  657.    Byte *bp = (Byte *)p, temp;
  658.  
  659.    temp = bp[0];
  660.    bp[0] = bp[1];
  661.    bp[1] = temp;
  662. }
  663.  
  664.  
  665.  
  666. void SwapDWord(void *p) {
  667.    Word *wp = (Word *)p, temp;
  668.  
  669.    temp = wp[0];
  670.    wp[0] = wp[1];
  671.    wp[1] = temp;
  672.    SwapWord(wp);
  673.    SwapWord(wp+1);
  674. }
  675. #endif   
  676.